/* 
 * File:   ext4.cpp
 * Author: lishang
 * 
 * Created on 2018年4月19日, 下午7:09
 */
#include "ext4.h"

class Ext4:public Nfs {
public:
    long config_jumpM;
    ext4_super_block sb; 
    ext4_inode *inode;
    DiskInfo dinfo;       //磁盘信息
    GroupInfo ginfo;   //块组信息
    vector <BlockGroup> groups;
    long postion,fileIndex;
    map <long,string>names;
    
    Ext4(){
        config_jumpM=16;
        fileIndex=0;
        names[2]=(char *)"/";
        
         char *data = ds->get(2);
//         ds->dump(data);
        sb = *((ext4_super_block *) data);
        dinfo.itemSize=pow(2 , sb.s_log_block_size+1);
        dinfo.size=sb.s_blocks_count_lo*dinfo.itemSize;
        ds->size = dinfo.size;
        
        ginfo.block = sb.s_blocks_per_group;
        ginfo.size = sb.s_blocks_per_group*dinfo.itemSize;
        ginfo.inode= sb.s_inodes_per_group;
        ginfo.count=sb.s_blocks_count_lo/ginfo.block+1;
        int size=sizeof(ext4_group_desc)/2;
        data = ds->get(dinfo.itemSize*(sb.s_first_data_block+1),size*ginfo.count);
//        ds->dump(data); 
        for(int i=0;i<ginfo.count;i++) {
            ext4_group_desc g=*((ext4_group_desc *)(data+i*size));

            long bg_inode_table = g.bg_inode_table_lo;
//            BlockGroup bg={
//                .postion = ginfo.size*i,
//                .inode= bg_inode_table * dinfo.itemSize,
//                .icount=ginfo.inode - g.bg_free_inodes_count_lo
//            };
            BlockGroup bg;
                bg.postion = ginfo.size*i;
                bg.inode= bg_inode_table * dinfo.itemSize;                      
                bg.icount=ginfo.inode - g.bg_free_inodes_count_lo;  
            groups.push_back(bg);
        }
        printf("磁盘类型Ext4, 磁盘总扇区:%ld(%s)  \n总块数：%ld,磁盘簇大小:%ld \n",dinfo.size,sizeFormat(dinfo.size),(long)sb.s_blocks_count_lo,dinfo.itemSize);
        dumpGroupInfo(&ginfo);
    }

    
    bool setJumpM(int){return true; }
    DiskInfo getDiskInfo(){      return dinfo;   }
    
    long next() {
        fileIndex++;
        while (!this->toIndex()) {
//            die(fileIndex,false);       ds->dump((char *)inode,128); getchar();
            if (postion > dinfo.size) return -1;
            fileIndex++;
            if(fileIndex > ginfo.count * ginfo.inode) return -1;
           continue;
        }
        
        return fileIndex;
    }

    
    FileInode getInode(long pos=0) {
        if(pos){
            fileIndex=pos;
            if(!this->toIndex(pos)){
                FileInode file;
                file.postion=-1;
                return file;
            }
        }
        
       int type=(inode->i_mode & 0xF000) >> 12;
        vector<long> child;        
        if(type == 4) {
            map <string, long> filesmap=this->listdir();
//            this->dumpMap(&filesmap);
            map <string, long>::iterator it;
            for(it=filesmap.begin();it!=filesmap.end();it++) {
                names[it->second]=it->first;
                child.push_back(it->second);
            }
        //    this->dumpMap(&names);die(names[31]);
        }

        string name=names.count(fileIndex) ? names[fileIndex] : "No Name";
        //{1:"先进先出(FIFO)",2:"字符设备",4:"目录",6:"块设备",  7:"正常文件",9:"动态链接",10:"软连",11:"套接字"}
        int tMap[12]={-1,11,22,-1,3,-1,66,1,1,99,10,11};
        
//        ds->dump((char *)inode,128);
//        dumpInode(inode);
        type=tMap[type];
        
        
          FileInode file;
              file.postion=fileIndex;
              file.size =inode->i_size_lo;
              file.type = type;
              file.child = child;
              file.name =(char *)name.data();
             file.parent = 0;
          
          return file;
    }

    string getData(FileInode file, long start=0, long len=0) {
        this->toIndex(file.postion);
        return this->getData(start,len);
    }

//    void dump(long pos,long size=0xFF) {
//        char *s=ds->get(pos);
//        ds->dump(s,size);
//    }
    void dump(long pos,long size=0xFF) {
        //dumpe2fs
//        ["引导块0~1k","超级块1～4k","块组描述符4～8k"];
//        ["块组位图0～4k","inode位图4～8k","inode表","数据块"];
         cout << "当前块组:" << pos/ginfo.size+1<< endl;
        char *p=ds->get(pos);
        char *start=p;
        int i;
        char str[20];  
        memset(str,0,sizeof(str));
        if(size==0) size=1024;
        int line=1;
        printf("\n 0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F \n");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0 && i != 0)  {
                printf("\t|%s|",str);
                cout << template_getNodes() << endl;
                str[0]='\0';
            }
            if (i % 16 == 8)  printf("  ");
            
            str[i%16]= isprint(p[0])?  p[0] :  '.' ;
            
            int pos=p-(char *) start;
//            int color=templateAttr[Template_MFT][pos];
//            setColor(0,color,color?true:false);

            printf("%2.2x ", /* (unsigned int) p & 0xff*/(unsigned char)*p++);
            //setColor();
        }
        
        printf("\t|%s|",str);
        cout << template_getNodes() << endl;
        return;
    }
private:
    bool toIndex(long index=0) {
        if(! index) index=fileIndex;
        index-=1;
        int bIndex=index/ginfo.inode;   //块索引
        int bPos=index%ginfo.inode;     //块偏移
        int size = sb.s_inode_size;             //inode节点大小 
        BlockGroup bg=groups.at(bIndex);
        
        postion=bg.inode+bPos*size/512;

        char *s=ds->get(postion);
        int sPos=bPos*size%512;
        inode =(ext4_inode *)(s+sPos);

//        
//        printf("\n%ld,%ld\n",index,postion);
//        if(index==11) ds->dump(s+sPos,size);
//        dumpInode(inode);
//        string data=this->getData();
//        ds->dump((char *)data.c_str());
        return inode->i_mode > 0 && inode->i_ctime>1000000000; //2001/9/9 9:46:40
    }
    
    void extent(char *extP,vector <long> *runs){
        ext4_extent_header *extHeader = (ext4_extent_header *)extP;
        if(extHeader->eh_magic !=  0xF30A || extHeader->eh_entries==0)    return ;
        
        int size = min(4,(int)extHeader->eh_max);
        
        char *bodys[500];
        char *p;
        for(int i=0;i<size;i++){
            p=  (char *)inode->i_block+12+i*12;
            bodys[i]=p;
        }

        p=bodys[extHeader->eh_entries-1];
//        ds->dump(extP,0x50);
//        printf("%p,%d,%d\n",p,extHeader->eh_entries,size);
        if(extHeader->eh_depth > 0){
            ext4_extent_idx *extIdx=(ext4_extent_idx *)p;
            long start =extIdx->ei_leaf_hi << 16 + extIdx->ei_leaf_lo;
            p=ds->get(start*dinfo.itemSize,dinfo.itemSize);
            this->extent(p,runs);
        }else{
            ext4_extent *ext= (ext4_extent *)p;
            long start =(ext->ee_start_hi << 16) + ext->ee_start_lo;
            int len = (ext->ee_len <= 32768 ) ? ext->ee_len : ext->ee_len-32768;
//            cout << "start:" <<ext->ee_start_hi<<"_"<<ext->ee_start_lo <<":"<< start <<" len:" << ext->ee_len<<endl;
            runs->push_back(start);runs->push_back(len);
        }
    }
    
    string getData_ext4(long start=0, long len=0) {   
        string fileData;
        
        vector <long> runs;
        this->extent((char *)inode->i_block,&runs);
        
        int count=0;
        int rsize= runs.size();
        //软连文件返回软连文件路径
        if(rsize==0 && ((inode->i_mode & 0xF000) >> 12 )==10)  fileData=(char *)inode->i_block;
//        this->dumpVector(&runs);
        
        for(int i=0;i<rsize;i+=2){
            long lcn=runs.at(i) * dinfo.itemSize;
            long size=runs.at(i+1) * dinfo.itemSize;
            
            //读取大小大于0 也就是说不是全部读取出来 
            if (len > 0) {
                count += size * 512;
                //跳过数据大于0 也就是说已经读取了一部分内容了 
                if (start > 0 && count < start) continue;
                long readNum = min(size * 512, len);
                fileData.append(ds->get(lcn, readNum / 512), readNum);
            } else{
                fileData.append(ds->get(lcn, size), size * 512);
            }
            if(len && count > start+len) break;
        }
        
        return fileData;
    }
    
    
    string getData_ext3(long start=0, long len=0) {   
        string fileData;
        int bsize = dinfo.itemSize*512;
        
        if(start ==0)
        for(int i=0;i<12;i++){
            if(inode->i_block[i]==0) return fileData;
            fileData.append(ds->get(inode->i_block[i] * dinfo.itemSize,dinfo.itemSize),dinfo.itemSize*512);
        }
        
        if(start==0 || (start > 12*bsize && start <bsize*bsize+12*bsize))
        // > 48k && < 16M
        if(inode->i_block[12]>0 && _getData(inode->i_block[12],&fileData)) {
            return fileData;
        }
        
        if(start==0  || (start > bsize*bsize+12*bsize && start < bsize^3+12*bsize))
        // > 16M && < 68G 
        if(inode->i_block[13] >0) {
            string pstr;
            _getData(inode->i_block[13],&pstr);
            bool rt=_getData(start - bsize*bsize+12*bsize,&fileData,pstr.c_str(),len);
            if(rt) return fileData;
        }
        
        if(start==0  || start > bsize^3+12*bsize)
        //>68G && < 272T
        if(inode->i_block[14] >0) {
            string ppstr,pstr;
            _getData(inode->i_block[14],&ppstr);
            _getData(start  ,&pstr,ppstr.c_str());
            _getData(start,&fileData,pstr.c_str(),len);
        }
        
        return fileData;
        
    }
    
    string getData(long start=0, long len=0) {           
        return this->getData_ext4(start,len);
    }
    bool _getData(long blockId,string *fileData,const char *s=NULL,long len=0){
        if(s ==NULL)      s=ds->get(blockId * dinfo.itemSize,dinfo.itemSize);
        long count=0;
        for(int i=0;i<dinfo.itemSize*512;i+=4){
            if(len>0) {
                count+=dinfo.itemSize;
                if(blockId>0 && count <blockId) continue;
            }
            long bIndex=*((long *)s+i);
            if(bIndex==0) return true;
            fileData->append(ds->get(bIndex * dinfo.itemSize,dinfo.itemSize),dinfo.itemSize);
        }
    }
   
    map <string, long> listdir(long index=0) {
        map <string, long> rt;
        ext4_dir_entry *dir;
        string dirData= this->getData();
        char *p=(char *)dirData.c_str();
        
        char *end=p+dirData.size();
        while(p<end){
            dir=(ext4_dir_entry *)p;
            p+=dir->rec_len;
            int len=dir->name_len & 0x00FF;
//            string name=Nfs::parseUNICODE((char *)dir->name,len);
           string name((char *)dir->name,len);
           rt[name]=dir->inode;
        }
        rt.erase(".");  rt.erase("..");
        
        return rt;
    }
    
    
        
};